﻿using System;
using System.Drawing;

namespace Zellularautomat.Structures
{
	/// <summary>
	/// Structure to define HSB.
	/// </summary>
	public struct HSBColor
	{
		/// <summary>
		/// Gets an empty HSB structure;
		/// </summary>
		public static readonly HSBColor Empty = new HSBColor();

		private double hue;
		private double saturation;
		private double brightness;

		public static bool operator ==(HSBColor item1, HSBColor item2)
		{
			return (
				item1.Hue == item2.Hue
				&& item1.Saturation == item2.Saturation
				&& item1.Brightness == item2.Brightness
				);
		}

		public static bool operator !=(HSBColor item1, HSBColor item2)
		{
			return (
				item1.Hue != item2.Hue
				|| item1.Saturation != item2.Saturation
				|| item1.Brightness != item2.Brightness
				);
		}

		/// <summary>
		/// Gets or sets the hue component.
		/// </summary>
		public double Hue
		{
			get
			{
				return hue;
			}
			set
			{
				hue = (value > 360) ? 360 : ((value < 0) ? 0 : value);
			}
		}

		/// <summary>
		/// Gets or sets saturation component.
		/// </summary>
		public double Saturation
		{
			get
			{
				return saturation;
			}
			set
			{
				saturation = (value > 1) ? 1 : ((value < 0) ? 0 : value);
			}
		}

		/// <summary>
		/// Gets or sets the brightness component.
		/// </summary>
		public double Brightness
		{
			get
			{
				return brightness;
			}
			set
			{
				brightness = (value > 1) ? 1 : ((value < 0) ? 0 : value);
			}
		}

		/// <summary>
		/// Creates an instance of a HSB structure.
		/// </summary>
		/// <param name="h">Hue value.</param>
		/// <param name="s">Saturation value.</param>
		/// <param name="b">Brightness value.</param>
		public HSBColor(double h, double s, double b)
		{
			hue = (h > 360) ? 360 : ((h < 0) ? 0 : h);
			saturation = (s > 1) ? 1 : ((s < 0) ? 0 : s);
			brightness = (b > 1) ? 1 : ((b < 0) ? 0 : b);
		}

		public override bool Equals(Object obj)
		{
			if (obj == null || GetType() != obj.GetType()) return false;

			return (this == (HSBColor)obj);
		}

		public override int GetHashCode()
		{
			return Hue.GetHashCode() ^ Saturation.GetHashCode() ^
				Brightness.GetHashCode();
		}

		/// <summary>
		/// Converts RGB to HSB.
		/// </summary>
		public static HSBColor FromRGB(Color color)
		{
			// normalize red, green and blue values
			double r = ((double)color.R / 255.0);
			double g = ((double)color.G / 255.0);
			double b = ((double)color.B / 255.0);

			// conversion start
			double max = Math.Max(r, Math.Max(g, b));
			double min = Math.Min(r, Math.Min(g, b));

			double h = 0.0;
			if (max == r && g >= b)
			{
				h = 60 * (g - b) / (max - min);
			}
			else if (max == r && g < b)
			{
				h = 60 * (g - b) / (max - min) + 360;
			}
			else if (max == g)
			{
				h = 60 * (b - r) / (max - min) + 120;
			}
			else if (max == b)
			{
				h = 60 * (r - g) / (max - min) + 240;
			}

			double s = (max == 0) ? 0.0 : (1.0 - (min / max));

			return new HSBColor(h, s, (double)max);
		}

		/// <summary>
		/// Converts HSB to RGB.
		/// </summary>
		public Color ToRGB()
		{
			double r = 0;
			double g = 0;
			double b = 0;

			if (saturation == 0)
			{
				r = g = b = brightness;
			}
			else
			{
				// the color wheel consists of 6 sectors. Figure out which sector
				// we're in.
				double sectorPos = hue / 60.0;
				int sectorNumber = (int)(Math.Floor(sectorPos));
				// get the fractional part of the sector
				double fractionalSector = sectorPos - sectorNumber;

				// calculate values for the three axes of the color.
				double p = brightness * (1.0 - saturation);
				double q = brightness * (1.0 - (saturation * fractionalSector));
				double t = brightness * (1.0 - (saturation * (1 - fractionalSector)));

				// assign the fractional colors to r, g, and b based on the sector
				// the angle is in.
				switch (sectorNumber)
				{
					case 0:
						r = brightness;
						g = t;
						b = p;
						break;
					case 1:
						r = q;
						g = brightness;
						b = p;
						break;
					case 2:
						r = p;
						g = brightness;
						b = t;
						break;
					case 3:
						r = p;
						g = q;
						b = brightness;
						break;
					case 4:
						r = t;
						g = p;
						b = brightness;
						break;
					case 5:
						r = brightness;
						g = p;
						b = q;
						break;
					case 6:
						r = brightness;
						g = t;
						b = p;
						break;
				}
			}

			return Color.FromArgb(
				Convert.ToInt32(Double.Parse(String.Format("{0:0.00}", r * 255.0))),
				Convert.ToInt32(Double.Parse(String.Format("{0:0.00}", g * 255.0))),
				Convert.ToInt32(Double.Parse(String.Format("{0:0.00}", b * 255.0)))
			);
		}
	}
}
